2-11 索引签名类型

绝大多数情况下,我们都可以在使用对象前就确定对象的结构,并为对象添加准确的类型。但是有些情况,我们无法确定对象中有哪些属性(或者说对象中只有一部分属性可以确定,但是其余的属性不能确定,我们在使用的时候会添加属性),此时,就需要使用索引签名类型了。

image-20251102200509194

因为JS中对象的键是 string 类型的,所以[key:string]可以表示对象中的所有键。包括函数哦,不要忘了,对象中也可以定义函数,函数名就是key,也是string类型。

 

在JS中,数组是一类特殊的对象,特殊的地方:数组的键(索引)是数值类型。并且,数组中可以出现任意多个元素。所以在数组对应的泛型接口中,也用到了索引签名类型。比如说下面我们自己定义一个数组类型。

image-20251102202142340

2-12 映射类型(Mapped Types)

基本使用

作用:基于旧的类型,创建新的类型(对象类型),减少重复,提升开发效率。

核心思想是:“循环遍历原类型的所有属性,对每个属性做统一的类型转换,最终得到新类型”,就像用 “模板” 批量处理类型,避免重复写冗余代码。

核心语法:{ [P in K]: T}

在这里的in表示遍历,[P in K]遍历逻辑:P 是 “当前属性名”(占位符,可自定义),K 是 “要遍历的属性集”(通常使用 keyof 原类型来生成属性集),将遍历到的属性 P 的类型,改为 T

in 在 TS 中的核心含义的是 “遍历” 或 “存在判断”,对应两个核心场景:

  1. 映射类型中:[P in K] → 遍历 K 中的所有属性(最常用);
  2. 类型守卫中:属性 in 对象 → 判断属性是否存在,缩小类型范围。

image-20251102204311541

在老师的例子中,PropKeys就是属性集。

结合keyof创建映射类型

上一个例子中,PropKeys是联合类型,但最常见的就是从一个对象类型里面获取属性集,这时候就要使用keyof来获取所有键的联合类型。

image-20251102205436735

分析泛型工具类型Partial的实现

image-20251102210602161

image-20251102210609997

索引查询类型基本使用

image-20251102210939462

索引查询类型,同时查询多个

可以在[]里面使用联合类型或者keyof操作符,来查询多个索引的类型。

image-20251102211313842

2-13 TypeScript类型声明文件

在学习Material UI的时候,在最后一节Custumize Theme里面,新增了一些样式,但是新增的样式不在MUI定义的ts类型里面,这时候怎么解决呢?老师新增了一个src/theme.d.ts文件,在里面写了一些ts的代码,但这些代码是怎么起作用的,我是完全没有头绪。

我的疑问就是:这种写法真的不会覆盖原来定义的ts类型吗?但是看实际的效果,我觉得是为这些interface新增了属性类型。但是怎么实现的,我心里总是没底,所以干脆学习一下,以后肯定能够用到的。

这里面用到了“声明合并”的功能,https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation这里可以参考。其实了解清楚了就非常简单,比如:

➡️ TypeScript 会把你的定义与原始模块 some-lib 的定义合并。 之后 Options 类型包含 debugverbose

概述

image-20251102212033709

TS中的两种文件类型

image-20251103091101510

当在.d.ts文件中写可执行代码的时候,会报错:

image-20251103091837872

image-20251103091848532

在全局上下文中不能声明实现。

类型声明文件的使用说明

image-20251103092141024

使用已有的类型声明文件

内置类型声明文件

image-20251103092553853

第三方库的类型声明文件

库自带类型声明文件

image-20251103092857681

由DefinitelyTyped提供

现在绝大多数库都提供自带的类型声明文件,这个地址提供的只是很少的一部分。注意:TS官方文档中提供的页面,现在已经404了。

GitHub - DefinitelyTyped/DefinitelyTyped: The repository for high quality TypeScript type definitions.

image-20251103093127448

创建自己的类型声明文件

在实际使用中,需要自己创建类型声明文件有两种情况:1、项目内需要共享类型;2、为已有的JS文件提供类型声明。

项目内共享类型

image-20251103094429833

为已有JS文件提供类型声明

这个也分为2部分。

image-20251103094829308

image-20251103095812307

这个内容还是蛮重要的,我在react项目、nextjs项目、nestjs项目中,都可以新建一个index.d.ts文件,在这个文件里面写项目需要的ts类型,避免临时写类型,这样代码就不好看。如果是多个人共同协作的项目,编写类型声明文件的时候,就可以使用拆分。

🧩 一、类型声明文件为什么容易冲突

当多人维护一个大的 global.d.tstypes.d.ts 文件时:

👉 解决思路:让类型声明模块化、可组合化。


📁 二、正确的拆分方式

✅ 方式 1:基于功能模块拆分

这是最推荐的方式,类似源码模块化。

index.d.ts

或使用 import/export 聚合(如果是模块化声明):

⚠️ 注意:如果项目用 "typeRoots""include" 指定了类型路径, 需要确保 tsconfig.json 包含这些文件。


✅ 方式 2:使用 declare module 拆分

如果是对第三方库(如 MUI、Express、Axios)进行扩展,可以每个库单独一个文件:

这样每个模块都独立声明、自动合并,不会冲突。


✅ 方式 3:为全局声明定义独立命名空间

如果必须写全局类型(如全局变量、工具函数),建议用命名空间隔离:

使用时:

这样不会污染全局命名空间。


🧠 三、多人协作时的规范建议

问题解决方案
不同人改同一文件拆分到独立的模块 .d.ts 文件
命名重复统一命名空间或类型前缀,例如 UserInfo, UserApiResponse
类型引用混乱建立统一的 types/index.ts 导出入口
版本不一致在 PR 审查阶段严格检查类型变更影响
类型测试使用 tsdvitest + expectTypeOf 做类型测试

📦 四、tsconfig.json 配置示例


✅ 小结

做法说明
按功能模块拆分 .d.ts最基础、最有效的防冲突方式
使用 declare module 扩展三方类型自动声明合并,安全
使用命名空间隔离全局类型避免污染
建立统一 types/index.ts 出口便于统一管理和导入
tsconfig 明确 typeRoots 路径确保编译器能识别

为已有JS文件提供类型声明,这个很简单,就是使用declare关键字来将类型声明一下,然后使用es6提供的模块化方案来导出定义好的类型,最后在文件里面引入并使用即可。

image-20251103102136517

类型扩展

https://www.typescriptlang.org/docs/handbook/declaration-merging.html

🧩 一、类型扩展的几种主要方式

✅ 1. 使用 extends 继承接口

extends 类似于「面向对象的继承」: 子接口拥有父接口的所有属性,并可以新增自己的。


✅ 2. 使用交叉类型 & 合并

💡 & 的效果类似“交集”,结果类型需要同时满足左右两边的结构。 常用于“组合多个类型”或“给库类型补充属性”。


✅ 3. 扩展第三方库类型(声明合并)

🔥 这是在实际项目里非常常见的一种「类型扩展」。

然后在组件中:

⚙️ TypeScript 会自动“合并”同名 declare module 的接口声明。


✅ 4. 扩展全局类型(declare global

这样在代码中你可以直接使用:

💡 declare global 适合扩展环境对象(Window, NodeJS.ProcessEnv 等)。


✅ 5. 泛型约束中的扩展

这里的 extends 表示“T 必须至少包含 name”。


✅ 6. Utility 类型中的扩展技巧

有时我们还会通过工具类型进行“选择性扩展”:


🧠 二、扩展 vs 合并 的区别

概念写法适用场景是否创建新类型
继承 (extends)interface B extends A派生新接口✅ 是
交叉类型 (&)type C = A & B组合类型✅ 是
声明合并declare module / 同名接口扩展已有类型(特别是库)❌ 否(原地合并)
全局扩展 (declare global)interface Window {}为环境补充类型❌ 否

🧩 三、MUI 类型扩展示例(实际项目中的用法)

假设你想给 MUI Theme 增加一个 status 颜色:

在主题定义里:

现在 TS 就能识别 theme.status.danger 的类型了 ✅。


🧩 四、实践建议

目标推荐做法
扩展自己的类型使用 extends&
扩展第三方库使用 declare module "xxx"
扩展全局对象使用 declare global
组合类型使用交叉类型 &
多人维护时避免冲突拆分不同 .d.ts 文件独立扩展

2-14 类型别名 和 交叉类型

类型别名 Type Aliases

https://www.typescriptlang.org/docs/handbook/advanced-types.html#type-aliases

类型别名,就是给类型起一个别名,让它可以更方便的被重用。使用 type 关键字。

这样就产生了一个疑问:type和interface的区别是什么?哪里应该用type,哪里应该用interface?

参考:https://zhuanlan.zhihu.com/p/351213183#:~:text=Typescript%20%E4%B8%AD%20type%20%E5%92%8C%20interface%20%E6%9C%89%E4%BB%80%E4%B9%88%E5%8C%BA%E5%88%AB%EF%BC%9F%20Almost%20all,which%20is%20always%20extendable.%20%E6%84%8F%E6%80%9D%E5%B0%B1%E6%98%AF%E8%AF%B4%E5%87%A0%E4%B9%8E%E6%89%80%E6%9C%89%E6%8E%A5%E5%8F%A3%E7%9A%84%E5%8A%9F%E8%83%BD%E9%83%BD%E5%8F%AF%E4%BB%A5%E5%9C%A8%E7%B1%BB%E5%9E%8B%E4%B8%AD%E4%BD%BF%E7%94%A8%EF%BC%8C%E4%B8%BB%E8%A6%81%E5%8C%BA%E5%88%AB%E5%9C%A8%E4%BA%8E%EF%BC%9A%20%E6%8E%A5%E5%8F%A3%E6%98%AF%E9%80%9A%E8%BF%87%E7%BB%A7%E6%89%BF%E7%9A%84%E6%96%B9%E5%BC%8F%E6%9D%A5%E6%89%A9%E5%B1%95%EF%BC%8C%E7%B1%BB%E5%9E%8B%E5%88%AB%E5%90%8D%E6%98%AF%E9%80%9A%E8%BF%87%20%26%20%E6%9D%A5%E6%89%A9%E5%B1%95%E3%80%82

先搞简单点:

Almost all features of an interface are available in type, the key distinction is that a type cannot be re-opened to add new properties vs an interface which is always extendable.

主要区别是:

 

上面的“接口通过继承的方式来扩展”,在interface这一节中并没有专门讲,但是通过泛型与接口已经讲到了,那么能不能定义一个接口,里面使用interface的extends来扩展呢?

可以这样写:

image-20240103151252670

要结合泛型一起使用才行:

image-20240103133352401

交叉类型 Intersection Types

https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html#intersection-types

交叉类型将多种类型组合为一种。这使您可以将现有类型加在一起,以获得具有所需所有功能的单个类型。

 

2-15 内置类型

内置类型

操作DOM和BOM时,一定要为变量定义类型,不然定义的事件都不能使用。也不要太担心,应该会有代码提示的,如果没有代码提示,可以console.dir(变量名),来查看这个变量是什么类型。

Utility Types

Typescript 还提供了一些功能性,帮助性的类型,这些类型,大家在 js 的世界是看不到的,这些类型叫做 utility types,提供一些简洁明快而且非常方便的功能。

TS独特关键字

🧩 一、类型系统相关关键字

关键字含义示例
type定义类型别名ts type Point = { x: number; y: number }; const p: Point = { x: 10, y: 20 };
interface定义接口结构(可被类实现或扩展)ts interface Person { name: string; age: number; } const p: Person = { name: 'Tom', age: 20 };
implements类实现接口ts interface Flyable { fly(): void; } class Bird implements Flyable { fly() { console.log('flying'); } }
enum枚举类型ts enum Direction { Up, Down, Left, Right } const dir = Direction.Up;
readonly只读属性ts interface User { readonly id: number; name: string; } const u: User = { id: 1, name: 'Tom' }; // u.id = 2 ❌
keyof获取类型的键名集合interface User { id: number; name: string; } type Keys = keyof User; // 'id'
typeof获取变量或类型的类型ts const user = { name: 'Tom', age: 20 }; type UserType = typeof user;
infer条件类型中推断类型ts type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never; type R = ReturnType<() => string>; // string
is自定义类型守卫ts function isString(x: unknown): x is string { return typeof x === 'string'; }
as类型断言ts const value: unknown = 'abc'; const len = (value as string).length;
satisfies检查对象是否满足特定类型(不改变推断)下面有
in用于类型映射或枚举属性下面有
extends泛型约束或类继承ts function logLength<T extends { length: number }>(arg: T) { console.log(arg.length); }
abstract定义抽象类或方法ts abstract class Shape { abstract area(): number; } class Circle extends Shape { constructor(private r: number) { super(); } area() { return Math.PI * this.r ** 2; } }
declare声明全局变量/模块/类型(不生成 JS 代码)ts declare const VERSION: string; // 告诉 TS 存在一个全局变量 VERSION
namespace定义命名空间(旧式模块组织方式)ts namespace MathUtil { export const PI = 3.14; export function square(x: number) { return x * x; } } console.log(MathUtil.square(2));
never不会返回的类型ts function fail(msg: string): never { throw new Error(msg); }
unknown不确定但安全的类型(比 any 更安全)ts let x: unknown = 'hello'; if (typeof x === 'string') console.log(x.toUpperCase());
any任意类型(跳过类型检查)ts let value: any = 42; value = 'hi'; value.toFixed();
void无返回值类型ts function log(msg: string): void { console.log(msg); }

🧩 一、satisfies — “类型符合检查”(TS 4.9+)

✅ 用途

satisfies 用来验证某个值是否满足指定类型约束,但 不会改变 该值的推断类型。 这比 as 更安全(as 是强制断言,satisfies 是类型验证)。


✅ 示例 1:验证对象是否满足接口要求

satisfies 不会把 config 的类型缩窄成 ApiConfig 仍然保留了额外属性(如 headers),仅验证其“满足”接口要求。


⚠️ 对比 as


✅ 示例 2:校验文字类型而不丢失推断

satisfies 在这里让 TypeScript 保留了字面量类型 "dark" 而不是自动放宽为 string,这有助于类型安全。


🧮 二、in — “映射类型”或“属性遍历”

✅ 用途

**in** 用在类型定义里,表示遍历联合类型的每个成员 通常与索引签名结合,构造新类型。


✅ 示例 1:从键集合生成类型


✅ 示例 2:生成布尔标志类型


✅ 示例 3:条件映射 + in


✅ 示例 4:in 也能用于循环(运行时)

⚠️ 注意:这里的 in 是 JS 层面的,不同于上面的类型用法。


✅ 总结对比表

关键字作用层面用途是否影响推断类型
satisfies类型检查阶段检查值是否满足某个类型,但不改变类型推断❌ 不会改变
in类型定义(映射类型)遍历联合类型的成员生成结构化类型✅ 会生成新类型结构

🧠 二、TypeScript 专属关键字的核心逻辑

分类描述
类型定义type, interface, enum
类型操作keyof, typeof, infer, extends, in, is, as, satisfies
修饰符readonly, abstract, implements
声明用途declare, namespace
类型级别any, unknown, never, void

🧩 三、综合示例(多个关键字结合)

TS独特符号

🧩 一、先说结论

✅ TypeScript 是 JavaScript 的超集 所以它 包含了全部 JS 的符号 并且 额外增加了一些类型系统专用符号(如 :, ?, |, &, as, !, <> 等)。

换句话说:


🧱 二、JavaScript 中的符号(TS 同样支持)

分类符号示例
算术运算符+ - * / % **a + b
递增/递减++ --i++
赋值运算符= += -= *= /= %= **=x += 5
比较运算符== != === !== > < >= <=a === b
逻辑运算符&& || !a && b
位运算符& | ^ ~ << >> >>>a << 2
条件运算符?:a > 0 ? 1 : 0
展开与剩余...{ ...obj } / (...args)
对象与数组解构{} []const {x, y} = p
模板字符串${}Hello ${name}
可选链?.obj?.a?.b
空值合并??value ?? 'default'
new / delete / typeof / instanceof / in与 JS 相同typeof x === 'string'

这些是 TypeScript 完全继承的符号。 TS 对这些符号的 运行时语义 与 JS 完全一致。


🧩 三、TypeScript 新增或扩展的符号(类型系统部分)

符号含义示例JS 中是否存在
:声明类型let x: number = 5;
?可选属性 / 参数interface User { name?: string }
|联合类型(或)let id: string | number; 
&交叉类型(与)type A = B & C;
<T>泛型声明function foo<T>(arg: T) {}
as类型断言value as string❌(JS 中仅语法无效)
!非空断言user!.name
?:映射类型中的可选修饰{ [K in Keys]?: string }
-?映射类型中的移除可选修饰{ [K in Keys]-?: string }
keyof取类型的键名type K = keyof User
typeof取类型(不同于 JS 的 typeof)type T = typeof obj✅(语义不同)
infer在条件类型中推断类型T extends infer U ? U : never
extends类型约束 / 类继承<T extends object>✅(类继承同名,但语义扩展)
in用于映射类型{ [K in keyof T]: T[K] }✅(语义扩展)
is类型守卫x is string
satisfies类型符合检查obj satisfies Config
readonly只读修饰符readonly name: string
abstract抽象类/方法abstract class Shape✅(ES2022+ 才支持)
implements实现接口class A implements B {}
declare声明存在的全局变量/模块declare const VERSION: string;
namespace命名空间namespace MathUtil { ... }
=>箭头函数(a: number) => a + 1✅(JS 有)

🧮 四、举例说明关键区别

1️⃣ : —— 类型注解

JS 中 : 没有此语法。


2️⃣ | & & —— 联合与交叉类型

JS 中 |& 仅用于位运算, TS 中用于类型组合。


3️⃣ as —— 类型断言

JS 没有类型系统,所以 as 无意义。


4️⃣ ! —— 非空断言

告诉编译器 “我确定这里不是 null/undefined”。 JS 仅有逻辑非 !,语义不同。


5️⃣ <T> —— 泛型声明

JS 中 < > 仅用于比较运算。 TS 中 <T> 表示类型参数。


6️⃣ keyof / in / extends

这些符号仅存在于 TS 类型层级中。


7️⃣ satisfies

JS 无法进行这种类型验证。


🧠 五、总结对比表

分类JavaScriptTypeScript 新增 / 扩展
运行时符号`+ - * / % && 
类型声明:?、`
类型操作keyoftypeof(扩展)、inferextendsinissatisfies
修饰符readonlyabstractimplementsdeclarenamespace

 

小结:

学了第二章的内容,发现TS为各种数据类型做了约束,这不就是C或JAVA语言里面的规范,怎么又加进来了吗?

TS中规定了很多类型,好处就是在我写ts代码的时候,必须按照定义的类型来写,不然在编写代码的时候就会报错,就是在写代码的时候就告诉我,这个地方要注意。

这在写别的代码的时候都没有的情况,别的代码写出来之后,最多检查一下语法规范什么的,不会对类型这方面的东西给出提示,但是ts中开启了监视功能之后,很快就能给出提示。不会出现一种数据类型使用另外一种数据类型方法的错误,而JS只有在执行之后才能告诉我这种错误。保持了代码的一致性,避免JS那种找不到错误的情况。